/**
 * Created on May 11, 2007
 */
package jvs.vfs.tools;

import java.awt.Desktop;
import java.awt.Desktop.Action;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;

import jvs.vfs.util.Log;
import x.java.io.File;
import x.java.lang.Runtime;
import x.java.lang.System;

/**
 * @author qiangli
 * 
 */
public class Shell {
	private static final String MAGIC_SIG = "#!"; 
	private static final String EXEC_MAGIC = "#!/bin/exec";
	
	private static final String MAGIC_CONF = "/etc/application/magic.conf";
	private static final String EXT_CONF = "/etc/application/ext.conf";
	//private static final String DEFAULT_CONF = "/etc/application/default.conf";
	
	/**
	 * @param string
	 * @throws Exception
	 */
	public static void exec(String cmd) throws Exception {
		exec(cmd, null);
	}
	
	/**
	 * @param string
	 * @param strings
	 * @throws Exception
	 */
	public static void exec(String cmd, String[] args) throws Exception {
		String sa[] = split3(cmd); // [path] cmd [ext]
		if (sa[0] != null) {
			exec(sa, args);
			return;
		}
		//TODO search user-defined path
		String[] paths = x.java.lang.System.getProperty("path", "/bin/").split(
				File.pathSeparator);
		for (int i = 0; i < paths.length; i++) {
			sa[0] = paths[i];
			if (exec(sa, args)) {
				return;
			}
		}
	}

	private static boolean exec(String[] sa, String[] args) throws Exception {
		String path = sa[0];
		if (!sa[0].endsWith("/")) {
			path = path+"/";
		}
		File f = null;
		if (sa[2] == null) {
			// #!/bin/exec <classname>
			f = new File(path + sa[1]);
			if (f.exists()) {
				String c = f.getContent().trim();
				String[] a = parseArgs(c.substring(EXEC_MAGIC.length()));
				String[] ca = a;
				if (args != null && args.length > 0) {
					ca = new String[a.length + args.length];
					System.arraycopy(a, 0, ca, 0, a.length);
					System.arraycopy(args, 0, ca, a.length, args.length);
				}
				Runtime.getRuntime().exec(ca);
				return true;
			}
		} else {
			//TODO test this
			// class
			//if (sa[1] != null && sa[2].equals(".class")) {
			//	f = new File(path + sa[1] + ".class");
			//	if (f.exists()) {
			//		run(path + sa[1], args);
			//	}
			//	return true;
			//}
			// ignore other extensions
			throw new RuntimeException("Unsupported file extension: " + sa[2]);
		}
		return false;
	}

	private static boolean isMagic(File f) throws IOException {
		InputStream in = null;
		try {
			if (f.isDirectory()) {
				return false;
			}
			in = f.getInputStream();
			byte[] ba = new byte[64];
			in.read(ba);
			in.close();
			int idx = 0;
			while(idx < ba.length && (ba[idx] == '\r' || ba[idx] == '\n' || ba[idx] == ' ')) idx++;
			if (idx < ba.length) {
				String s = new String(ba,idx,ba.length-idx);
				return (s.startsWith(MAGIC_SIG));
			}
			return false;
		} finally{
			if (in != null) {
				in.close();
			}
		}
	}
	
	/**
	 * File is opened by:
	 * 1. magic -- /etc/application/magic.conf
	 * 2. extension -- /etc/application/ext.conf
	 * 3. default -- /etc/application/default.conf
	 * 
	 * @param f
	 * @return
	 * @throws Exception
	 */
	public static boolean launch(File f) throws Exception {
		try {
			if (isMagic(f)) {
				String c = f.getContent().trim();;
				String[] sa = parseScript(c);
				String magic = sa[0];
				String[] apps = read(magic,MAGIC_CONF);
				if (apps.length == 0) {
					apps = new String[] {"notepad"};//
				}
				String[] args = null;
				if (sa[1] != null) {
					args = parseArgs(sa[1]);
				} else {
					args = new String[]{f.getPath()};
				}
				String[] args2 = args;
				if (sa[2] != null) {
					args2 = new String[args.length+1];
					System.arraycopy(args,0, args2, 0, args.length);
					args2[args.length] = sa[2]; //script content
				}
				exec(apps[0],args2);
				return true;
			}
			//
			String ext = f.getExt();
			if ((ext == null || ext.length() == 0) && f.isDirectory()) {
				ext = "folder";
			}
			if(ext != null && ext.length() != 0) {
				String[] apps = read(ext,EXT_CONF);
				String[] args = new String[]{f.getPath()};
				exec(apps[0],args);
				return true;
			}
			//default.conf
			//TODO - generic launcher/application mapper
			//return false;
			//exec("notepad",new String[]{f.getPath()});
			
			Desktop dt = Desktop.getDesktop();
			if (f.isDirectory() && dt.isSupported(Action.BROWSE)) {
				dt.browse(f.toRealURI());
				return true;
			} 
			if (f.isFile() && dt.isSupported(Action.OPEN)) {
				dt.open(nativeFile(f));
				return true;
			} 
			return false;
		} finally {
		}
	}

	private static java.io.File nativeFile(File file) throws IOException {
		x.java.io.File f = (x.java.io.File) file;
		URI uri = f.getNativeURI();//f.toRealURI();
		return new java.io.File(URLDecoder.decode(uri.getPath(),"UTF-8"));
	}
	
	public static void main(String[] args) throws IOException {
		//[file,classname,args....]
		Log.log(Log.DEBUG,Shell.class.getName(),Arrays.asList(args));
		if (args.length > 0 && args[0].equals(Shell.class.getName())) {
			System.err().println("Ignored: "+Arrays.asList(args));
			return;
		}
		Runtime.getRuntime().exec(args);
	}
	
	/**
	 * Parse string into array of magic, args, script
	 * @param s
	 * @return
	 */
	private static String[] parseScript(String s) {
		//read first line
		int idx = 0;
		int len = s.length();
		while(idx < len) {
			if (s.charAt(idx) == '\r' || s.charAt(idx) == '\n') {
				break;
			}
			idx++;
		}
		String line = s.substring(2,idx); //#!<magic> args\r\n
		String script = null;
		while(idx<len && Character.isSpaceChar(s.charAt(idx))) idx++;
		if (idx < len) {
			script = s.substring(idx+1).trim();
		}
		String magic = line;
		String args =  null;
		idx = line.indexOf(' ');
		if (idx > 0) {
			magic = line.substring(0,idx);
			args =  line.substring(idx);
		}
		return new String[]{magic,args,script};
	}

	private static String[] parseArgs(String s) {
			if (s == null) {
				return new String[0];
			}
			s = s.trim();
			if (s.length() == 0) {
				return new String[0];
			}
			char[] ca = s.toCharArray();
			//
			List<String> wordlist = new ArrayList<String>();
			int wordstart = 0;
			boolean quoted = false;
			for (int i=wordstart; i<ca.length;) {
				if (ca[i] == '\\') {
					i += 2;
					continue;
				}
				if(ca[i] == '"') {
					if (quoted) {
						i++;
						wordlist.add(s.substring(wordstart,i));
						while(i<ca.length && Character.isSpaceChar(ca[i])) i++;
						wordstart = i;
						quoted = false;
						continue;
					} else {
						i++;
						quoted = true;
						continue;
					}
				}
				if (quoted) {
					i++;
					continue;
				}
				if (Character.isSpaceChar(ca[i])) {
					String word = s.substring(wordstart,i);
					wordlist.add(word);
					while(i<ca.length && Character.isSpaceChar(ca[i])) i++;
					wordstart = i;
					continue;
				}
				i++;
			}
			//if any remaining
			if (wordstart < ca.length) {
				String word = s.substring(wordstart, ca.length).trim();
				if (word.length()>0) {
					wordlist.add(word);
				}
			}
			
			x.java.lang.System.out().println(s+" => "+wordlist);
			return (String[])wordlist.toArray(new String[0]);
		
	}

	private static String[] read(String key,String filename) {
		//TODO cache results???
		File f = new File(filename);
		Properties p = new Properties();
		InputStream is = null;
		try {
			is = f.getInputStream();
			p.load(is);
		} catch (Exception e) {
			try {
				is.close();
			} catch (Exception ex) {
			}
		}
		String val = p.getProperty(key);
		if (val == null) {
			return new String[0];
		}
		StringTokenizer st = new StringTokenizer(val,",");
		String[] va = new String[st.countTokens()];
		for(int i=0; st.hasMoreTokens(); i++) {
			va[i] = st.nextToken();
		}
		return va;
	}
	
	protected static void run(String cmd, String[] args) throws Exception {
		String[] ca = (args == null ? new String[1]
				: new String[args.length + 1]);
		ca[0] = cmd;
		if (args != null && args.length > 0) {
			System.arraycopy(args, 0, ca, 1, args.length);
		}
		Runtime.getRuntime().exec(ca);

	}
	
	/**
	 * @param string
	 * @return
	 */
	protected static String[] splitx(String str) {
		StringTokenizer st = new StringTokenizer(str);
		String[] sa = new String[st.countTokens()];
		for (int i = 0; i < sa.length; i++) {
			sa[i] = st.nextToken();
		}
		return sa;
	}

	private static String[] split3(String name) {
		String[] sa = new String[3]; // path cmd ext
		int idx = name.lastIndexOf("/");
		if (idx > 0) {
			sa[0] = name.substring(0, idx + 1);
			name = name.substring(idx + 1);
		}
		idx = name.lastIndexOf(".");
		if (idx > 0) {
			sa[1] = name.substring(0, idx);
			sa[2] = name.substring(idx);
		} else {
			sa[1] = name;
		}
		return sa;
	}

	private Shell() {
	}
}
